package com.omni.monitor.plugin.trace_monitor;

import com.omni.monitor.model.OmniConst;
import com.omni.monitor.model.trace_monitor.TraceNode;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.lang.reflect.Field;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;


/**
 * MyBatis SQL拦截器：拦截SQL执行，关联全链路调用链
 */
@Component
@Intercepts({
        // 拦截查询操作（query方法）
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
        // 拦截增删改操作（update方法）
        @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
})
public class MyBatisSqlInterceptor implements Interceptor {
    private static final Logger log = LoggerFactory.getLogger(MyBatisSqlInterceptor.class);
    // 敏感字段脱敏（避免日志泄露，可根据业务扩展）
    private static final List<String> SENSITIVE_FIELDS = Arrays.asList("password", "mobile", "idCard", "bankCard");
    private static final Map<String, Field> FIELD_CACHE = new ConcurrentHashMap<>();
    // 脱敏替换符
    private static final String MASK = "******";
    /**
     * 默认线程栈数组下标
     */
    private static final int DEFAULT_INDEX = 2;
    @Resource
    private TraceContext traceContext;

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 1. 获取拦截参数：MappedStatement（包含SQL信息）、参数对象
        Object[] args = invocation.getArgs();
        MappedStatement ms = (MappedStatement) args[0];
        Object parameter = args[1];
        // 获取SQL ID（Mapper全路径+方法名，如com.xxx.mapper.OrderMapper.insertOrder）
        String sqlId = ms.getId();
        // 获取原始SQL（带?占位符）
        BoundSql boundSql = ms.getBoundSql(parameter);
        String originalSql = boundSql.getSql().replaceAll("\\s+", " "); // 去除多余空格，优化日志格式

        // 2. 进入SQL调用链：生成SQL专属span
        TraceNode sqlNode = traceContext.enterSql(sqlId, originalSql);

        long startTime = System.currentTimeMillis();
        Object result = null;
        boolean isSuccess = true;
        try {
            // 3. 执行原始SQL操作
            result = invocation.proceed();
        } catch (Exception e) {
            isSuccess = false;
            log.error("SQL执行异常：sqlId={}，originalSql={}，错误信息={}", sqlId, originalSql, e.getMessage(), e);
            throw e;
        } finally {
            // 4. 计算耗时，生成完整SQL（替换?为实际参数）
            long costTime = System.currentTimeMillis() - startTime;
            // 提取SQL参数
            List<Object> parameters = extractParameters(boundSql, parameter);
            String fullSql = formatSqlWithParameters(boundSql.getSql(), parameters);
            // 5. 退出SQL调用链：关联完整SQL和耗时到调用链
            traceContext.exitSql(sqlNode, fullSql, costTime, isSuccess);
        }
        return result;
    }

    /**
     * 打标
     * 1. 要清楚的知道被执行的 SQL 是定义在 Mapper 中的哪条
     * 2. 要清楚的知道这条 SQL 被执行时方法的调用栈
     */
    private void marking(BoundSql boundSql, MappedStatement mappedStatement) throws NoSuchFieldException, IllegalAccessException {

        // 实际的 SQL
        String sql = boundSql.getSql().trim();
        // 只对 select 打标
        if (sql.toLowerCase().contains("select")) {
            // 获取其基类中的 MappedStatement 即定义的 SQL 声明对象，获取它的 ID 值表示它是哪条 SQL
//            MappedStatement mappedStatement = getFieldValue(
//                    BaseStatementHandler.class, delegate, "mappedStatement", MappedStatement.class
//            );
            String mappedStatementId = mappedStatement.getId();
            // 方法调用栈
            String trace = traceContext.trace();

            // 按顺序创建打标的内容
            LinkedHashMap<String, Object> markingMap = new LinkedHashMap<>();
            markingMap.put("STATEMENT_ID", mappedStatementId);
            markingMap.put(OmniConst.TRACE_ID_MDC_KEY, MDC.get(OmniConst.TRACE_ID_MDC_KEY));
            markingMap.put("STACK_TRACE", trace);
            String marking = "[SQLMarking] ".concat(markingMap.toString());

            // 打标\
            sql = String.format(" /* %s */ %s", marking, sql);

            // 反射更新
            Field field = getField(BoundSql.class, "sql");
            field.set(boundSql, sql);
        }
    }

    private Field getField(Class<?> clazz, String fieldName) throws NoSuchFieldException {
        Field field;
        String cacheKey = String.format("%s.%s", clazz.getName(), fieldName);
        if (FIELD_CACHE.containsKey(cacheKey)) {
            field = FIELD_CACHE.get(cacheKey);
        } else {
            field = clazz.getDeclaredField(fieldName);
            field.setAccessible(true);
            FIELD_CACHE.put(cacheKey, field);
        }
        return field;
    }

    /**
     * 格式化SQL语句，替换参数占位符
     *
     * @param sql        原始SQL
     * @param parameters 参数列表
     * @return 格式化后的SQL
     */
    private String formatSqlWithParameters(String sql, List<Object> parameters) {
        if (sql == null || parameters == null || parameters.isEmpty()) {
            return sql;
        }

        try {
            String formattedSql = sql;

            for (Object parameter : parameters) {
                String paramValue;
                if (parameter == null) {
                    paramValue = "NULL";
                } else if (parameter instanceof String) {
                    paramValue = "'" + parameter.toString().replace("'", "''") + "'";
                } else if (parameter instanceof Date) {
                    paramValue = "'" + parameter.toString() + "'";
                } else {
                    paramValue = parameter.toString();
                }

                formattedSql = formattedSql.replaceFirst("\\?", paramValue);
            }

            return formattedSql;

        } catch (Exception e) {
            log.warn("格式化SQL时发生异常", e);
            return sql;
        }
    }

    /**
     * 提取SQL参数
     *
     * @param boundSql  BoundSql对象
     * @param parameter 参数对象
     * @return 参数列表
     */
    private List<Object> extractParameters(BoundSql boundSql, Object parameter) {
        List<Object> parameters = new ArrayList<>();

        try {
            List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
            if (parameterMappings != null && !parameterMappings.isEmpty() && parameter != null) {
                // 使用 SystemMetaObject 创建 MetaObject，避免依赖 Configuration
                MetaObject metaObject = SystemMetaObject.forObject(parameter);

                for (ParameterMapping parameterMapping : parameterMappings) {
                    String propertyName = parameterMapping.getProperty();
                    Object value;

                    if (metaObject.hasGetter(propertyName)) {
                        value = metaObject.getValue(propertyName);
                    } else if (boundSql.hasAdditionalParameter(propertyName)) {
                        value = boundSql.getAdditionalParameter(propertyName);
                    } else {
                        value = null;
                    }

                    parameters.add(value);
                }
            }
        } catch (Exception e) {
            log.warn("提取SQL参数时发生异常", e);
        }

        return parameters;
    }

    /**
     * 格式化参数值（处理集合、数组、日期等特殊类型）
     */
    private Object formatParamValue(Object value) {
        if (value == null) {
            return "NULL";
        }
        // 处理集合（如List、Set）
        if (value instanceof Collection) {
            Collection<?> coll = (Collection<?>) value;
            List<Object> formattedList = new ArrayList<>();
            for (Object item : coll) {
                formattedList.add(formatParamValue(item));
            }
            return formattedList;
        }
        // 处理数组
        if (value.getClass().isArray()) {
            Object[] arr = (Object[]) value;
            List<Object> formattedList = new ArrayList<>();
            for (Object item : arr) {
                formattedList.add(formatParamValue(item));
            }
            return formattedList;
        }
        // 处理日期（格式化为yyyy-MM-dd HH:mm:ss）
        if (value instanceof Date) {
            return new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format((Date) value);
        }
        // 敏感字段已在前面处理，此处直接返回
        return value;
    }

    /**
     * 找到字符串中第n个目标字符的索引（从后往前找）
     */
    private int indexOf(String str, char target, int n) {
        int count = 0;
        for (int i = str.length() - 1; i >= 0; i--) {
            if (str.charAt(i) == target) {
                count++;
                if (count == n) {
                    return i;
                }
            }
        }
        return -1;
    }

    /**
     * 生成代理对象（MyBatis要求，固定写法）
     */
    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    /**
     * 配置拦截器参数（此处无需配置，留空）
     */
    @Override
    public void setProperties(Properties properties) {
    }
}